using Clancy.Common;
using Clancy.Interfaces;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Shapes;
using Rectangle = System.Drawing.Rectangle;

namespace Clancy.Bl
{
    internal class MotionZoneFactory : IMotionZoneFactory
    {
        public Rectangle[] CalculateZones(Polygon zonePolygon, int boxSize)
        {
            if ((zonePolygon == null) || (zonePolygon.Points.Count < 3))
            {
                return new Rectangle[] { };
            }

            List<Rectangle> zones = new List<Rectangle>();

            int xMin = zonePolygon.Points.Min(p => (int)p.X);
            int xMax = zonePolygon.Points.Max(p => (int)p.X);

            int yMin = zonePolygon.Points.Min(p => (int)p.Y);
            int yMax = zonePolygon.Points.Max(p => (int)p.Y);

            Dictionary<int, List<Point>> cutPoints = new Dictionary<int, List<Point>>();

            FindAllCutpoints(zonePolygon, boxSize, xMax, xMin, yMin, yMax, cutPoints);

            RemoveUnwantedCutpoints(cutPoints);

            foreach (int x in cutPoints.Keys)
            {
                if (cutPoints[x].Count != 2)
                    continue;

                Point p1 = cutPoints[x][0];
                Point p2 = cutPoints[x][1];
                if ((Math.Abs(p1.X - p2.X) < 0.5) && (Math.Abs(p1.Y - p2.Y) > 0.5))
                {
                    Rectangle zone = CreateZone(boxSize, p1, p2);
                    zones.Add(zone);
                }
            }

            return zones.ToArray();
        }

        private static int ComparePointByYPosition(Point p1, Point p2)
        {
            return p1.Y.CompareTo(p2.Y);
        }

        private static Rectangle CreateZone(int boxSize, Point p1, Point p2)
        {
            Rectangle zone = new Rectangle(p1.X, p1.Y, boxSize, p2.Y - p1.Y);
            if (zone.Width < 0)
            {
                int newWidth = -1 * zone.Width;
                zone.X = zone.X - newWidth;
                zone.Width = newWidth;
            }

            if (zone.Height < 0)
            {
                int newHeight = -1 * zone.Height;
                zone.Y = zone.Y - newHeight;
                zone.Height = newHeight;
            }
            return zone;
        }

        private static bool IsCutPointInRange(Point cutPoint, Point polyLineStart, Point polyLineEnd)
        {
            int xStart = polyLineStart.X;
            int yStart = polyLineStart.Y;

            int xEnd = polyLineEnd.X;
            int yEnd = polyLineEnd.Y;

            MakeAscendingOrdner(ref xStart, ref xEnd);
            MakeAscendingOrdner(ref yStart, ref yEnd);

            int x = cutPoint.X;
            int y = cutPoint.Y;

            return ((x >= xStart) && (x <= xEnd) && (y >= yStart) && (y <= yEnd));
        }

        private static void MakeAscendingOrdner(ref int x1, ref int x2)
        {
            if (x1 > x2)
            {
                int temp = x2;
                x2 = x1;
                x1 = temp;
            }
        }

        private static bool MakeCutPoint(Point line1Start, Point line1End, Point line2Start, Point line2End, out Point cutPoint)
        {
            cutPoint = new Point(0, 0);

            int a1, a2, b1, b2, c1, c2;
            MakeLinePolynom(line1Start, line1End, out a1, out b1, out c1);
            MakeLinePolynom(line2Start, line2End, out a2, out b2, out c2);

            int delta = a1 * b2 - a2 * b1;
            if (Math.Abs(delta - 0) <= 0)
                return false;

            int x = (b2 * c1 - b1 * c2) / delta;
            int y = (a1 * c2 - a2 * c1) / delta;

            cutPoint = new Point((int)x, (int)y);
            return true;
        }

        private static void MakeLinePolynom(Point start, Point end, out int a, out int b, out int c)
        {
            a = end.Y - start.Y;
            b = start.X - end.X;
            c = a * start.X + b * start.Y;
        }

        private static void RemoveUnwantedCutpoints(Dictionary<int, List<Point>> cutPoints)
        {
            foreach (int xPos in cutPoints.Keys)
            {
                cutPoints[xPos].Sort(ComparePointByYPosition);
                while (cutPoints[xPos].Count > 2)
                {
                    cutPoints[xPos].RemoveAt(1);
                }
            }
        }

        private void FindAllCutpoints(Polygon zonePolygon, int boxSize, int xMax, int xMin, int yMin, int yMax, Dictionary<int, List<Point>> cutPoints)
        {
            int offset = ((xMax - xMin) % boxSize / 2);
            for (int i = 0; i < zonePolygon.Points.Count; i++)
            {
                Point polyLineStart = zonePolygon.Points[i].ToDrawingPoint();
                Point polyLineEnd = i < zonePolygon.Points.Count - 1 ? zonePolygon.Points[i + 1].ToDrawingPoint() : zonePolygon.Points[0].ToDrawingPoint();

                int pos = (int)(xMin + offset);
                while (pos < xMax)
                {
                    Point scanLineStart = new Point(pos, yMin - 10);
                    Point scanLineEnd = new Point(pos, yMax + 20);

                    Point cutPoint;
                    if (MakeCutPoint(scanLineStart, scanLineEnd, polyLineStart, polyLineEnd, out cutPoint))
                    {
                        if (!cutPoints.ContainsKey(pos))
                            cutPoints[pos] = new List<Point>();

                        if (IsCutPointInRange(cutPoint, polyLineStart, polyLineEnd))
                            cutPoints[pos].Add(cutPoint);
                    }

                    pos += boxSize;
                }
            }
        }
    }
}